package com.hqd.ch03.v31.tx.transaction.interceptor;

import com.hqd.ch03.utils.ClassUtils;
import com.hqd.ch03.v31.aware.BeanFactoryAware;
import com.hqd.ch03.v31.aware.InitializingBean;
import com.hqd.ch03.v31.factory.BeanFactory;
import com.hqd.ch03.v31.thread.NamedThreadLocal;
import com.hqd.ch03.v31.tx.transaction.PlatformTransactionManager;
import com.hqd.ch03.v31.tx.transaction.TransactionManager;
import com.hqd.ch03.v31.tx.transaction.TransactionStatus;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;

@Data
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
    private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
            new NamedThreadLocal<>("Current aspect-driven transaction");
    private String transactionManagerBeanName;

    private TransactionManager transactionManager;

    private TransactionAttributeSource transactionAttributeSource;

    private BeanFactory beanFactory;

    protected static TransactionInfo currentTransactionInfo() {
        return transactionInfoHolder.get();
    }

    public static TransactionStatus currentTransactionStatus() {
        TransactionInfo info = currentTransactionInfo();
        if (info == null || info.transactionStatus == null) {
            throw new RuntimeException("No transaction aspect-managed TransactionStatus in scope");
        }
        return info.transactionStatus;
    }

    protected Object invokeWithinTransaction(Method method, Class<?> targetClass,
                                             final InvocationCallback invocation) throws Throwable {

        /**
         * 获取事务信息
         */
        // 事务配置信息管理器
        TransactionAttributeSource tas = getTransactionAttributeSource();
        /**
         * 获取事务配置信息：
         * 	事务隔离级别
         * 	事务传播行为
         * 	事务超时时间
         * 	是否只读事务
         */
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        final TransactionManager tm = determineTransactionManager(txAttr);

        PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
        /**
         * 获取方法签名：默认类全路径+方法名
         */
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        /**
         * 创建事务信息
         */
        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            retVal = invocation.proceedWithInvocation();
        } catch (Throwable ex) {
            /**
             * 回滚事务
             */
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        } finally {
            /**
             * 清理事务信息
             */
            cleanupTransactionInfo(txInfo);
        }
        /**
         * 提交事务
         */
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }

    protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
        if (txInfo != null && txInfo.getTransactionStatus() != null) {
            txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
        }
    }

    protected void cleanupTransactionInfo(TransactionInfo txInfo) {
        if (txInfo != null) {
            txInfo.restoreThreadLocalStatus();
        }
    }

    protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
        if (txInfo != null && txInfo.getTransactionStatus() != null) {
            if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
                try {
                    txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
                } catch (RuntimeException | Error ex2) {
                    throw ex2;
                }
            } else {
                try {
                    txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
                } catch (RuntimeException | Error ex2) {
                    throw ex2;
                }
            }
        }
    }

    protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm,
                                                           TransactionAttribute txAttr, final String joinpointIdentification) {

        /**
         * 封装事务信息
         */
        if (txAttr != null && txAttr.getName() == null) {
            txAttr = new DelegatingTransactionAttribute(txAttr) {
                @Override
                public String getName() {
                    return joinpointIdentification;
                }
            };
        }

        /**
         * 获取事务
         */
        TransactionStatus status = null;
        if (txAttr != null) {
            if (tm != null) {
                status = tm.getTransaction(txAttr);
            }
        }
        /**
         * 准备事务信息
         */
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }

    protected TransactionInfo prepareTransactionInfo(PlatformTransactionManager tm,
                                                     TransactionAttribute txAttr, String joinpointIdentification,
                                                     TransactionStatus status) {

        TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
        if (txAttr != null) {
            txInfo.newTransactionStatus(status);
        }

        txInfo.bindToThread();
        return txInfo;
    }

    private String methodIdentification(Method method, Class<?> targetClass,
                                        TransactionAttribute txAttr) {
        return ClassUtils.getQualifiedMethodName(method, targetClass);
    }

    private PlatformTransactionManager asPlatformTransactionManager(Object transactionManager) {
        if (transactionManager == null || transactionManager instanceof PlatformTransactionManager) {
            return (PlatformTransactionManager) transactionManager;
        } else {
            throw new IllegalStateException(
                    "Specified transaction manager is not a PlatformTransactionManager: " + transactionManager);
        }
    }

    protected TransactionManager determineTransactionManager(TransactionAttribute txAttr) {
        if (txAttr == null || this.beanFactory == null) {
            return getTransactionManager();
        }

        if (StringUtils.isNotBlank(this.transactionManagerBeanName)) {
            return (TransactionManager) this.beanFactory.getBean(this.transactionManagerBeanName);
        } else {
            return this.beanFactory.getBean(TransactionManager.class);
        }
    }

    @Override
    public void afterPropertiesSet() {
        if (getTransactionManager() == null && this.beanFactory == null) {
            throw new IllegalStateException(
                    "Set the 'transactionManager' property or make sure to run within a BeanFactory " +
                            "containing a TransactionManager bean!");
        }
        if (getTransactionAttributeSource() == null) {
            throw new IllegalStateException(
                    "Either 'transactionAttributeSource' or 'transactionAttributes' is required: " +
                            "If there are no transactional methods, then don't use a transaction aspect.");
        }
    }

    protected interface CoroutinesInvocationCallback extends InvocationCallback {

        Object getTarget();

        Object[] getArguments();

        default Object getContinuation() {
            Object[] args = getArguments();
            return args[args.length - 1];
        }
    }

    @FunctionalInterface
    protected interface InvocationCallback {

        Object proceedWithInvocation() throws Throwable;
    }

    /**
     * 事务运行时所有东西
     */
    protected static final class TransactionInfo {

        private final PlatformTransactionManager transactionManager;

        private final TransactionAttribute transactionAttribute;

        private final String joinpointIdentification;

        private TransactionStatus transactionStatus;

        private TransactionInfo oldTransactionInfo;

        public TransactionInfo(PlatformTransactionManager transactionManager,
                               TransactionAttribute transactionAttribute, String joinpointIdentification) {

            this.transactionManager = transactionManager;
            this.transactionAttribute = transactionAttribute;
            this.joinpointIdentification = joinpointIdentification;
        }

        public PlatformTransactionManager getTransactionManager() {
            return this.transactionManager;
        }

        public TransactionAttribute getTransactionAttribute() {
            return this.transactionAttribute;
        }

        public String getJoinpointIdentification() {
            return this.joinpointIdentification;
        }

        public void newTransactionStatus(TransactionStatus status) {
            this.transactionStatus = status;
        }

        public TransactionStatus getTransactionStatus() {
            return this.transactionStatus;
        }

        public boolean hasTransaction() {
            return (this.transactionStatus != null);
        }

        private void bindToThread() {
            this.oldTransactionInfo = transactionInfoHolder.get();
            transactionInfoHolder.set(this);
        }

        private void restoreThreadLocalStatus() {
            transactionInfoHolder.set(this.oldTransactionInfo);
        }

    }
}
